Flutter 树形控件 TreeView,快速实现一个树形菜单

您所在的位置:网站首页 flutter 懒加载图片 Flutter 树形控件 TreeView,快速实现一个树形菜单

Flutter 树形控件 TreeView,快速实现一个树形菜单

2023-03-24 22:21| 来源: 网络整理| 查看: 265

项目中用到树形菜单,我采用第三方控件 flutter_treeview 实现,为了画出符合 UI 设计图的树形结构,花了一天时间翻看源码,总算大概熟悉了它的使用,这篇文章做个总结。

其实树形菜单的实现主要有两个难点,将数据处理成树状菜单结构、对控件设置样式。

处理数据

树形菜单控件的节点类 Node 如下

class Node { ///节点唯一标识key final String key; ///节点标题,上海工行 final String label; ///图标 final IconData? icon; ///图标颜色 final Color? iconColor; ///节点被选择时颜色 final Color? selectedIconColor; ///是否展开 final bool expanded; final T? data; ///子节点 final List children; ///是否为父节点 final bool parent; }

假设接口返回数据如下,相信很容易理解,这种父子结构数据其实很常见,像一些省市区、权限管理数据设计,都是这样的。

const List STATIONS = [ { "stationId": 2, "stationName": "上海工行", "parentId": 0 }, { "stationId": 3, "stationName": "黄埔支行", "parentId": 2 }, { "stationId": 123456, "stationName": "黄埔大街支行", "parentId": 3 }, { "stationId": 123455, "stationName": "黄埔新城支行", "parentId": 3 }, { "stationId": 4, "stationName": "徐汇支行", "parentId": 2 }, { "stationId": 5, "stationName": "长宁支行", "parentId": 2 }, { "stationId": 11, "stationName": "宝山支行", "parentId": 2 }, { "stationId": 12, "stationName": "嘉定支行", "parentId": 2 }, { "stationId": 33, "stationName": "山西工行", "parentId": 0 }, { "stationId": 22, "stationName": "太原支行", "parentId": 33 } ];

现在的问题是如何处理上面的数据,处理成节点类 List。我这分享两种方案,你们根据自己实际情况,做适当修改。

static List createNodeList(List data, int parentId) { List nodeList = []; for (final item in data) { if (item['parentId'] == parentId) { List children = createNodeList(data, item['stationId']); nodeList.add( Node( key: item['stationId'].toString(), label: item['stationName'], children: children, parent: children.isNotEmpty, icon: children.isEmpty ? Icons.star : Icons.account_balance_sharp, ), ); } } return nodeList; } static List loadNodes(){ return createNodeList(STATIONS, 0); }

第二种方案

///获取根节点,可能是一个,可能是多个,但统一用集合接收 static List rootNode() { List rootList = data.where((e) => !data.any((ee) => ee["stationId"] == e["parentId"])).toList(); return rootList; } ///传入父节点 static Node buildNode(Map element){ List children = data .where((e) => e["parentId"] == element["stationId"]) .map((e) => buildNode(e)) .toList(); ///先构造父节点 Node node = Node( key: element["stationId"], label: element["stationName"], icon: children.isEmpty ? Icons.star : Icons.account_balance_sharp, children: children); return node; } List nodes = []; List rootList = rootNode(); rootList.forEach((element) { Node node = buildNode(element); nodes.add(node); });

处理完后,数据会以树形结构构造。

设置 TreeView

到上面一步,我们已经可以将树状图显示在界面,只不过样式都是默认的,可能不好看。

class MyTreeViewPage extends StatefulWidget { @override State createState() => _MyTreeViewPageState(); } class _MyTreeViewPageState extends State { String _selectedNode = ""; List nodes = []; TreeViewController? _treeViewController; @override void initState() { super.initState(); nodes = TreeViewUtil.loadNodes(); log(nodes.toString()); _treeViewController = TreeViewController( children: nodes, selectedKey: _selectedNode, ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("树形菜单"), ), body: Container( child: TreeView( controller: _treeViewController!, ), ), ); } }

设置样式关键是要熟悉 TreeViewTheme 各个属性含义。先构造出来设置到 TreeView。

TreeView( controller: _treeViewController!, theme: _initTreeViewTheme(), ), TreeViewTheme _initTreeViewTheme(){ TreeViewTheme treeViewTheme = TreeViewTheme( ); return treeViewTheme; }

接下来就是看 TreeViewTheme 各个属性。

class TreeViewTheme { final ColorScheme colorScheme; final double levelPadding; ///和listview中dense一样,当true时,整体同比缩小 final bool dense; ///每个Node之间垂直间隔 final double? verticalSpacing; ///每个Node之间水平间隔距离,设置成100,自己看看就懂了 final double? horizontalSpacing; ///图标icon的左右间距 final double iconPadding; ///设置图标样式(大小和颜色) final IconThemeData iconTheme; ///展开项设置,这个属性很重要 final ExpanderThemeData expanderTheme; ///文本样式 final TextStyle labelStyle; ///父文本样式 final TextStyle parentLabelStyle; final TextOverflow? labelOverflow; final TextOverflow? parentLabelOverflow; final Duration expandSpeed; }

上面有三个属性是比较重要的,先看图标样式 IconThemeData

iconTheme: IconThemeData( size: 36, color: Colors.blue ),

然后是展开项 ExpanderThemeData,包括

expanderTheme: const ExpanderThemeData( type: ExpanderType.arrow, modifier: ExpanderModifier.circleFilled, position: ExpanderPosition.start, size: 20, color: Colors.red ),

那几个枚举里属性都可以尝试看看,当然,你也可以不要这个展开图标,ExpanderType.none 设置后其他属性都会失效。

type: ExpanderType.none,

最后是节点的点击事件

onNodeTap: (key){ setState(() { _selectedNode = key; _treeViewController = _treeViewController!.copyWith(selectedKey: key); ... }); },

OK,以上就是我要分享的,希望能帮到你。

本文由老郭种树原创,转载请注明:https://guozh.net/flutter-tree-view-widget/

相关文章推荐 Flutter 库的管理,包的导入和导出,解决包冲突问题 [异常] ProcessException: Process exited abnormally: Starting: Intent Flutter TabBar + TabBarView 实现顶部导航栏,懒加载数据和状态保活 Flutter 时间日期选择器 DatePicker 使用笔记 [异常] Invalid `Podfile` file: undefined method `exists?’ for File:Class [异常] Error installing cocoapods,Failed to build gem native extension


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3